iT邦幫忙

2022 iThome 鐵人賽

DAY 19
0
Modern Web

Hello TypeScript 菜鳥系列 第 19

Day 18. TypeScript Generic 泛型:超基本語法

  • 分享至 

  • xImage
  •  

在其他程式語言裡有一個蠻好用的語法,可以創造出有相同邏輯卻能套用在不同型別的函式(function),像是C++的template、C#和Java的Generic等,而TypeScript的類似語法同樣稱為Generic,先舉一個非常簡單的例子來認識TypeScript的Generic。

假設有個函式是可以接收number型別參數並回傳輸入的number型別參數:

function returnNumber(arg: number): number {
	return arg;
}

但在另一個情境則是需要類似的函式,只是改成接收string型別參數並回傳輸入的string型別參數:

function returnString(arg: string): string {
	return arg;
}

為了確保回傳值型別和參數型別相同,兩個函式都有將回傳值型別明確指定成和輸入參數型別相同的型別。

不過,寫程式有個很基本的原則稱為Dry原則 ─ 「Don't repeat yourself」,意即不要重複你所寫的程式碼。

從這裡可以發現,上述兩個函式雖然名稱和型別不同,但其實函式內部的邏輯都是一樣的 ─ 都只是在回傳相同型別的輸入參數,所以撇開型別來看,我們確實一直在重複相同的程式碼、做同樣的事情。

那有沒有辦法能夠寫出邏輯相同、只是套用在不同型別的函式?

此時可能會想到一種解決方式是 ─ 把型別改成 any 型別來避免重複程式碼的問題:

function returnAny(arg: any): any {
	return arg;
}

但是這種寫法會延伸出另一個問題:若函式邏輯愈來愈複雜,any 型別無法保證輸入參數的型別和回傳值的型別是否相同。

因此當我們希望有一種函式是可以用不同型別去執行一樣的事情,這種時候就可以用Generic來徹底解決這個問題。

Generic函式寫法如下:

function returnSomething<T>(arg: T): T {
	return arg;
}

範例的 <> 角括號是Generic用來接收型別變數的語法(類似於函式接收引數的 () ),角括號內的 T 就是用來表示某個未知型別的型別變數,而 T 究竟是何種型別要等到呼叫函式、輸入型別的時候才會知道。

譬如在函式角括號<T> T 的位置輸入number型別:

returnSomething<number>(1);

/* 
 *  相當於
 *  function returnSomething<number>(arg: number): number {
 *		return arg;
 *	}
 */

如果需要的是string型別函式就將 <T> 寫成 <string>

returnSomething<string>("Hello Generic");

/* 
 *  相當於
 *  function returnSomething<string>(arg: string): string {
 *		return arg;
 *	}
 */

如此一來,不僅能避免重複邏輯相同的程式碼,同時能保證輸入參數型別和回傳值型別一致,這邊簡單驗證一下:

let stringResult: string;

stringResult = returnSomething<number>(1);	// compile error

當然,如果是像上面這種簡單的Generic函式,也能省略指定型別 <T> 的步驟。

如下方例子,TypeScript Compiler 會自行推導(infer)回傳值是string型別:

let stringResult = returnSomething("Hello Generic");

console.log(typeof stringResult);	// "string"

此外,在寫函式內部邏輯時,如果已經明確寫出特定型別才有的屬性或方法,必須限制傳入的型別變數至少要有這個屬性或方法,例如以下函式的參數如果只給予型別變數 <T>,因為TypeScript Compiler無法保證輸入的型別有 length 這個屬性,就會在編譯期間報錯:

function getLength<T>(args: T): number {
	return args.length;	// compile error
}

所以必須明確給予至少有 length 屬性的generic型別,譬如 generic array:

function getLength<T>(args: T[]): number {
	return args.length;	// ok
}

或是寫成:

function getLength<T>(args: Array<T>): number {
	return args.length;	// ok
}

因為是菜鳥系列,不希望篇幅太長,所以今天簡單認識Generic,接下來預計還會有1~2篇繼續了解Generic的其他用法。


參考資料
Generic @TypeScript Handbook
TypeScript


上一篇
Day 17. TypeScript interface 介面:extends、implements
下一篇
Day 19. TypeScript Generic 泛型:Generic Interface、Generic Class
系列文
Hello TypeScript 菜鳥31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言